#include <array>
#include <cstring>
#include <iostream>

#include "ani_helpers_build_scripts.h"
#include "concurrency_helpers.h"
#include <ani.h>
#include <atomic>

static void impl_LoadClass([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_class cls, ani_string classNameAniStr) {
  ani_size strLen{};
  CHECK_ANI_FATAL(env->String_GetUTF8Size(classNameAniStr, &strLen));
  ++strLen;

  std::string className(strLen, '\0');
  ani_size wasWrote{};
  CHECK_ANI_FATAL(env->String_GetUTF8(classNameAniStr, className.data(), strLen, &wasWrote));

  // std::cout << "strLen " << strLen << std::endl;
  // std::cout << "wasWrote " << wasWrote << std::endl;
  // std::cout << "className: " << className.c_str() << std::endl;

  ani_class loadedCls;
  CHECK_ANI_FATAL(env->FindClass(className.c_str(), &loadedCls));
}

static void impl_LoadAXXClasses([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_class cls) {
  ani_class loadedCls;

  std::array classList = {
      "TstLib.A1",  "TstLib.A2",  "TstLib.A3",  "TstLib.A4",  "TstLib.A5",  "TstLib.A6",     "TstLib.A7",
      "TstLib.A8",  "TstLib.A9",  "TstLib.A10", "TstLib.A11", "TstLib.A12", "TstLib.A13",    "TstLib.A14",
      "TstLib.A15", "TstLib.A16", "TstLib.A17", "TstLib.A18", "TstLib.A19", "TstLib.A20",    "TstLib.A21",
      "TstLib.A22", "TstLib.A23", "TstLib.A24", "TstLib.A25", "TstLib.A26", "TstLib.A27",    "TstLib.A28",
      "TstLib.A29", "TstLib.A30", "TstLib.A31", "TstLib.A32", "TstLib.A33", "TstLib.A34",    "TstLib.A35",
      "TstLib.A36", "TstLib.A37", "TstLib.A38", "TstLib.A39", "TstLib.A40", "TstLib.TstLib",
  };

  for (const char *className : classList) {
    // std::cout << "className: " << className << std::endl;
    CHECK_ANI_FATAL(env->FindClass(className, &loadedCls));
  }
}

static void impl_LoadAXXClassesAsync([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_class cls) {
  std::cout << "start: impl_LoadAXXClassesAsync" << std::endl;

  using arkts::concurrency_helpers::AsyncWork;
  using arkts::concurrency_helpers::WorkStatus;

  AsyncWork *work{};
  WorkStatus status = CreateAsyncWork(
      env,
      [](ani_env *env, void *data) {
        std::cout << "start: ExecuteCallback" << std::endl;
        std::cout << "end: ExecuteCallback" << std::endl;
      },
      [](ani_env *env, WorkStatus status, void *data) {
        std::cout << "start: CompleteCallback" << std::endl;
        std::cout << "end: CompleteCallback" << std::endl;
      },
      nullptr, &work);

  std::cout << "worker status: " << (status == WorkStatus::OK ? "OK " : "FAILED ") << static_cast<int>(status)
            << std::endl;

  status = QueueAsyncWork(env, work);

  std::cout << "worker status: " << (status == WorkStatus::OK ? "OK " : "FAILED ") << static_cast<int>(status)
            << std::endl;

  std::cout << "end: impl_LoadAXXClassesAsync" << std::endl;
}

static void loadClassByBootLinker(ani_env *env, const char *className) {

  ani_module stdCoreModule{};
  CHECK_ANI_FATAL(env->FindModule("std.core", &stdCoreModule));

  ani_function getBootLinkerFunc{};
  CHECK_ANI_FATAL(env->Module_FindFunction(stdCoreModule, "getBootRuntimeLinker", ":C{std.core.BootRuntimeLinker}",
                                           &getBootLinkerFunc));

  ani_ref bootLinker{};
  CHECK_ANI_FATAL(env->Function_Call_Ref(getBootLinkerFunc, &bootLinker));

  ani_class bootRuntimeLinkerClass{};
  CHECK_ANI_FATAL(env->FindClass("std.core.BootRuntimeLinker", &bootRuntimeLinkerClass));

  ani_method findAndLoadClassMethod{};
  CHECK_ANI_FATAL(env->Class_FindMethod(bootRuntimeLinkerClass, "findAndLoadClass",
                                        "C{std.core.String}z:C{std.core.Class}", &findAndLoadClassMethod));

  ani_string aniClassName;
  CHECK_ANI_FATAL(env->String_NewUTF8(className, std::strlen(className), &aniClassName));

  ani_ref classInited{};
  CHECK_ANI_FATAL(env->Object_CallMethod_Ref(static_cast<ani_object>(bootLinker), findAndLoadClassMethod, &classInited,
                                             aniClassName, ANI_TRUE));

  ani_class loadedCls{};
  CHECK_ANI_FATAL(env->FindClass(className, &loadedCls));

  ani_class suparTrupa{};
  CHECK_ANI_FATAL(env->Type_GetSuperClass(loadedCls, &suparTrupa));

  // ani_static_method zzz{};
  // std::string signature = "C{";
  // signature += className;
  // signature += "}:";
  // env->Class_FindStaticMethod(loadedCls, "zzz", ":", &zzz);

  // ani_object obj{};
  // CHECK_ANI_FATAL(env->Object_New(loadedCls, nullptr, nullptr, &obj));
}

static void impl_LoadAXXClassesByLinker([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_class cls) {
  std::array classList = {
      "TstLib.A1",  "TstLib.A2",  "TstLib.A3",  "TstLib.A4",  "TstLib.A5",  "TstLib.A6",     "TstLib.A7",
      "TstLib.A8",  "TstLib.A9",  "TstLib.A10", "TstLib.A11", "TstLib.A12", "TstLib.A13",    "TstLib.A14",
      "TstLib.A15", "TstLib.A16", "TstLib.A17", "TstLib.A18", "TstLib.A19", "TstLib.A20",    "TstLib.A21",
      "TstLib.A22", "TstLib.A23", "TstLib.A24", "TstLib.A25", "TstLib.A26", "TstLib.A27",    "TstLib.A28",
      "TstLib.A29", "TstLib.A30", "TstLib.A31", "TstLib.A32", "TstLib.A33", "TstLib.A34",    "TstLib.A35",
      "TstLib.A36", "TstLib.A37", "TstLib.A38", "TstLib.A39", "TstLib.A40", "TstLib.TstLib",
  };

  for (const char *className : classList) {
    // std::cout << "className: " << className << std::endl;
    loadClassByBootLinker(env, className);
  }
}

static ani_long impl_AllocAtomic(ani_int initial) { return reinterpret_cast<ani_long>(new std::atomic_int(initial)); }

static ani_int impl_CompareAndSwapAtomic(ani_long rawReference, ani_int expectedValue, ani_int newValue) {
  auto *reference = reinterpret_cast<std::atomic_int *>(rawReference);
  return reference->compare_exchange_strong(expectedValue, newValue);
}

static void impl_FreeAtomic(ani_long rawReference) {
  auto *reference = reinterpret_cast<std::atomic_int *>(rawReference);
  delete reference;
}

ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result) {
  ani_env *env;
  CHECK_ANI_FATAL(vm->GetEnv(ANI_VERSION_1, &env));

  static const char *className = "TstLib.TstLib";
  ani_class cls;
  CHECK_ANI_FATAL(env->FindClass(className, &cls));

  std::array staticMethods = {
      ani_native_function{"LoadClass", "C{std.core.String}:", reinterpret_cast<void *>(impl_LoadClass)},
      ani_native_function{"LoadAXXClasses", ":", reinterpret_cast<void *>(impl_LoadAXXClasses)},
      ani_native_function{"LoadAXXClassesByLinker", ":", reinterpret_cast<void *>(impl_LoadAXXClassesByLinker)},
      ani_native_function{"LoadAXXClassesAsync", ":", reinterpret_cast<void *>(impl_LoadAXXClassesAsync)},
      ani_native_function{"AllocAtomic", "i:l", reinterpret_cast<void *>(impl_AllocAtomic)},
      ani_native_function{"CompareAndSwapAtomic", "lii:i", reinterpret_cast<void *>(impl_CompareAndSwapAtomic)},
      ani_native_function{"FreeAtomic", "l:", reinterpret_cast<void *>(impl_FreeAtomic)},
  };

  CHECK_ANI_FATAL(env->Class_BindStaticNativeMethods(cls, staticMethods.data(), staticMethods.size()));

  // std::array methods = {
  //     ani_native_function {"LoadAXXClassesAsync", ":", reinterpret_cast<void *>(impl_LoadAXXClassesAsync)},
  // };

  // CHECK_ANI_FATAL(env->Class_BindNativeMethods(cls, methods.data(), methods.size()));

  *result = ANI_VERSION_1;
  return ANI_OK;
}
